package com.ideaaedi.plugin.fastcoding.util;

import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiImportList;
import com.intellij.psi.PsiImportStatement;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.PsiLocalVariable;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiPackageStatement;
import com.intellij.psi.util.PsiTreeUtil;

import java.util.HashSet;
import java.util.Set;

/**
 * PsiJavaFile相关操作工具类
 *
 * @author JustryDeng
 * @since 2021/12/18 10:18:04
 */
public final class JdPsiFileUtil {
    
    /**
     * 往java文件中插入内容
     *
     * @param project
     *         当前项目
     * @param psiJavaFile
     *         java文件
     * @param offset
     *         插入起始位置
     * @param content
     *         要插入的内容
     */
    public static void insertContent(Project project, PsiJavaFile psiJavaFile, int offset, String content) {
        PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
        Document document = psiDocumentManager.getDocument(psiJavaFile);
        if (document == null) {
            return;
        }
        WriteCommandAction.runWriteCommandAction(project, () -> document.insertString(offset, content));
    }
    
    /**
     * 向指定java文件中添加导入
     *
     * @param project
     *         项目
     * @param psiJavaFile
     *         java文件
     * @param newImports
     *         需要添加的导入集合(即：全类名集合)
     */
    public static void addImport(Project project, PsiJavaFile psiJavaFile, Set<String> newImports) {
        PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
        Document document = psiDocumentManager.getDocument(psiJavaFile);
        if (document == null) {
            return;
        }
        // java.lang包下的类不需要导入 & 不包含点的即为基础原始类型或特殊类型，也不需要导入
        Set<Character> illegalCharSet = new HashSet<>();
        illegalCharSet.add(',');
        illegalCharSet.add('<');
        illegalCharSet.add('>');
        illegalCharSet.add('?');
        illegalCharSet.add('[');
        illegalCharSet.add(']');
        if (newImports.size() > 0) {
            newImports.removeIf(u -> {
                // 没有【.】，说明是primitive类型，需要移除
                if (!u.contains(".")) {
                    return true;
                }
                // java.lang包下的类不需要导入， 需要移除
                if (u.startsWith("java.lang.")) {
                    return true;
                }
                // 带有非法字符的，需要移除
                for (char c : u.toCharArray()) {
                    if (illegalCharSet.contains(c)) {
                        return true;
                    }
                }
                return false;
            });
        }
        if (newImports.size() == 0) {
            return;
        }
        
        // 已存在的import
        PsiImportList importList = psiJavaFile.getImportList();
        PsiImportStatement[] alreadyExistImports = importList == null ? new PsiImportStatement[0] :
                importList.getImportStatements();
        Set<String> alreadyExistImportSet = new HashSet<>();
        for (PsiImportStatement importStatement : alreadyExistImports) {
            alreadyExistImportSet.add(importStatement.getQualifiedName());
        }
        
        newImports.removeAll(alreadyExistImportSet);
        if (newImports.size() == 0) {
            return;
        }
        
        // 计算位置
        int start = 0;
        PsiPackageStatement packageStatement = psiJavaFile.getPackageStatement();
        if (packageStatement != null) {
            start = packageStatement.getTextLength() + packageStatement.getTextOffset();
        }
        int finalStart = start;
        
        // 拼接要插入的内容
        StringBuilder newImportStrBuilder = new StringBuilder();
        newImportStrBuilder.append("\n");
        for (String newImport : newImports) {
            newImportStrBuilder.append("\n").append("import ").append(newImport).append(";");
        }
        String newImportStr = newImportStrBuilder.toString();
        
        // 插入&提交&保存
        WriteCommandAction.runWriteCommandAction(project, () -> document.insertString(finalStart, newImportStr));
        commitAndSaveDocument(project, psiJavaFile);
    }
    
    /**
     * 提交并保存文件
     *
     * @param project
     *            项目
     * @param psiJavaFile
     *            java文件
     */
    public static void commitAndSaveDocument(Project project, PsiJavaFile psiJavaFile) {
        PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
        Document document = psiDocumentManager.getDocument(psiJavaFile);
        if (document == null) {
            throw new IllegalArgumentException("cannot find document.");
        }
        psiDocumentManager.doPostponedOperationsAndUnblockDocument(document);
        psiDocumentManager.commitDocument(document);
        FileDocumentManager.getInstance().saveDocument(document);
    }
    
    
    /**
     * 当前语法行的缩进
     *
     * @param project
     *            当前项目
     * @param psiMethod
     *            当前语法行所属方法
     * @param grammarBlock
     *            当前语法行
     * @return  当前语法行的左侧缩进
     */
    public static String determineIndentationStr(Project project, PsiMethod psiMethod, PsiElement grammarBlock) {
        StringBuilder indentationStrBuilder = new StringBuilder();
        PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
        
        if (psiMethod == null) {
            return indentationStrBuilder.toString();
        }
        Document document = psiDocumentManager.getDocument(psiMethod.getContainingFile());
        if (document == null) {
            return indentationStrBuilder.toString();
        }
        // 当前语法行代码
        int textStartOffset = grammarBlock.getTextRange().getStartOffset();
        String preChar = document.getText(new TextRange(textStartOffset - 1, textStartOffset));
        while (!"\n".equals(preChar)) {
            indentationStrBuilder.append(preChar);
            textStartOffset--;
            preChar = document.getText(new TextRange(textStartOffset - 1, textStartOffset));
        }
        return indentationStrBuilder.toString();
    }
}
